#include<stdio.h>
#include<sys/stat.h>
#include<strings.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<stdlib.h>
#include<wait.h>

#include"rio/rio.h"
#include"socket/socket.h"


static const MAXLINE = 100;
static const MAXBUF  = 8096;

void doit(int fd);
void read_requesthdrs(rio_t* rp);
int parse_uri(char* uri, char* filename, char* cgiargs);
void serve_static(int fd, char* filename, int filesize);
void get_filetype(char* filename, char* filetype);
void serve_dynamic(int fd, char* filename, char* cgiargs);
void clienterror(int fd, char* cause, char* errnum, char* shotmsg, char* longmsg);

int main(int argc, char const *argv[])
{
    int listenfd, connfd;
    char hostname[MAXLINE], port[MAXLINE];
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;

    if (argc != 2)
    {
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        return 0;
    }
    
    listenfd = open_listenfd(argv[1]);
    while (1)
    {
        clientlen = sizeof(clientaddr);
        connfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientlen);
        getnameinfo((struct sockaddr*)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
        printf("accepted connection from (%s, %s)\n", hostname, port);
        doit(connfd);
        close(connfd);
    }
    return 0;
}

void diot(int fd)
{
    int is_static;
    struct stat sbuf;
    char buf[MAXLINE],method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char filename[MAXLINE], cgiargs[MAXLINE];
    rio_t rio;


    rio_readinitb(&rio, fd);
    rio_readlineb(&rio, buf, MAXLINE);
    printf("request headers:\n");
    printf("%s",buf);
    sscanf(buf, "%s, %s, %S", method, uri, version);
    if (strcasecmp(method, "GET"))
    {
        clienterror(fd, method, "501", "not implemented", "tiny does not implement this method");
        return;
    }
    read_requesthdrs(&rio);
    is_static = parse_uri(uri, filename, cgiargs);
    if (stat(filename, &sbuf) < 0)
    {
        clienterror(fd, filename, "404", "not found", "tiny couldnot find this file");
        return;
    }
    if (is_static)
    {
        if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))
        {
            clienterror(fd, filename, "403", "forbidden", "tiny couldnot run the cui program");
            return;
        }
        serve_static(fd, filename, cgiargs);
    }else
    {
        if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode))
        {
            clienterror(fd, filename, "403", "forbidden", "tiny couldnot run the cgi  grogram");
        }
        serve_dynamic(fd, filename, cgiargs);
    }
}

void clienterror(int fd, char* cause, char* errnum, char* shotmsg, char* longmsg)
{
    char buf[MAXLINE], body[MAXBUF];
    sprintf(body, "<html><title>tiny error </title>");
    sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);
    sprintf(body, "%s%s: %s\r\n",body, errnum, shotmsg);
    sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
    sprintf(body, "%s<hr><em>the tiny web server</em>\r\n",body);

    sprintf(buf, "http/1.0 %s %s\r\n", errnum, shotmsg);
    rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-type: text/html\r\n");
    rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
    rio_writen(fd, buf, strlen(buf));
    rio_writen(fd, body, strlen(body));
}

void read_requesthdrs(rio_t* rp)
{
    char buf [MAXLINE];

    rio_readlineb(rp, buf, MAXLINE);
    while (strcmp(buf, "\r\n"))
    {
        rio_readlineb(rp, buf, MAXLINE);
        printf("%s", buf);
    }
    return;
}


int parse_uri(char* uri, char* filename, char* cgiargs)
{
    char* ptr;
    
    if (!strstr(uri, "cgi-bin"))
    {
        strcpy(cgiargs, "");
        strcpy(filename, ".");
        strcat(filename, uri);
        if (uri[strlen(uri)-1] == '/')
        {
            strcat(filename, "home.html");
        }
        return 1;
    }else
    {
        ptr = index(uri, '?');
        if (ptr)
        {
            strcpy(cgiargs, ptr+1);
            *ptr = '\0';
        }else
        {
            strcpy(cgiargs, "");
        }
        strcpy(filename, ".");
        strcat(filename, uri);
        return 0;
    }
}


void serve_static(int fd, char* filename, int filesize)
{
    int srcfd;
    char *srcp, filetype[MAXLINE],buf[MAXBUF];

    get_filetype(filename, filetype);
    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    sprintf(buf, "%sServer: tiny web server\r\n", buf);
    sprintf(buf, "%sConnection: close\r\n", buf);
    sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
    sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
    rio_writen(fd, buf, strlen(buf));
    printf("%s", buf);

    srcfd = open(filename, O_RDONLY, 0);
    srcp =  mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
    close(srcfd);
    rio_writen(fd, srcp, filesize);
    munmap(srcp, filesize);
}


void get_filetype(char* filename, char* filetype)
{
    if (strstr(filename, ".html"))
    {
        strcpy(filetype, "text/html");
    }else if (strstr(filename, ".gif"))
    {
        strcpy(filetype, "images/gif");
    }else if (strstr(filetype, ".png"))
    {
        strcpy(filetype, "images/png");
    }else if (strstr(filetype, ".jpg"))
    {
        strcpy(filetype, "images/gpeg");
    }else
    {
        strcpy(filetype, "text/plaint");
    }
}


void serve_dynamic(int fd, char* filename, char* cgiargs)
{
    char buf[MAXLINE], *emptylist[]={NULL};

    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Server: tiny web Server\r\n");
    rio_writen(fd, buf, strlen(buf));

    if (fork() == 0)
    {
        setenv("QUERY_STRING", cgiargs, 1);
        dup2(fd, STDOUT_FILENO);
        execve(filename, emptylist, __environ);
    }
    wait(NULL);
}